package com.habdqn.studentmanager.util;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.habdqn.studentmanager.bean.AttendanceMachine;
import com.habdqn.studentmanager.bean.IPInfo;
import com.habdqn.studentmanager.constant.ConnState;
import com.habdqn.studentmanager.handler.AttendanceHandler;
import com.habdqn.studentmanager.service.AttendanceService;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;

/**
 * @author Fan
 * 
 *         2017年7月4日
 * 
 */
public class AttendanceConnection extends Thread {

	private static final Logger logger = LoggerFactory.getLogger("AttendanceConnection");
	
	/**
	 * 机器编号
	 */
	private Integer machineId;
	
	/**
	 * 机器地址
	 */
	private String ipStr;
	
	/**
	 * 考勤机状态
	 */
	private Integer status;
	
	/**
	 * 重连次数
	 */
	private int reConnCount = 1;
	
	/**
	 * 考勤服务类
	 */
	private AttendanceService attendanceService;
	
	public AttendanceConnection(AttendanceMachine machine, AttendanceService attendanceService) {
		this.machineId = machine.getId();
		this.ipStr = machine.getAddress();
		this.attendanceService = attendanceService;
	}
	
	@Override
	public void run() {
		try {
		    this.setName("AttendanceConnection-" + machineId);
			status = ConnState.CONNECTING.toInt();
			IPInfo iPInfo = IPInfo.getIPInfo(ipStr);
			connec(iPInfo);
		}
		catch (Exception e) {
			status = ConnState.FAIL.toInt();
			logger.error("连接考勤机失败.{}", e.getMessage());
		}
	}

	/**
	 * 连接
	 * @throws InterruptedException 
	 */
	private void connec(IPInfo iPInfo) throws InterruptedException {
		
	    while(true) {
	        AttendanceHandler serviceHandler = new AttendanceHandler(this);
	        
	        String host = iPInfo.getAddress();
	        int port = iPInfo.getPort();
	        EventLoopGroup workerGroup = new NioEventLoopGroup();
	        try {
	            Bootstrap b = new Bootstrap();
	            b.group(workerGroup);
	            b.channel(NioSocketChannel.class);
	            b.option(ChannelOption.SO_KEEPALIVE, true);
	            b.handler(new ChannelInitializer<SocketChannel>() {
	                @Override
	                public void initChannel(SocketChannel ch) throws Exception {
	                    ch.pipeline().addLast(new LengthFieldBasedFrameDecoder(65535, 0, 4, 0, 4));
	                    ch.pipeline().addLast(serviceHandler);
	                }
	            });
	            
	            ChannelFuture f = b.connect(host, port).sync();
	            f.channel().closeFuture().sync();
	        }
	        catch (Exception e) {
	            logger.error("连接考勤机失败.{}", e.getMessage());
	            // 标记为失败
	            this.status = ConnState.FAIL.toInt();
	        }
	        finally {
	            workerGroup.shutdownGracefully();
	            
	            // 标记为重连
                this.status = ConnState.RECONNECTING.toInt();
	            addReConnCount();
	            long sleepTime = 5000 * reConnCount;
	            
	            if (sleepTime < 0) {
	                clearReConnCount();
	            }
	            
	            logger.debug("准备重连考勤机, {}, sleepTime:{}", iPInfo, sleepTime);
	            Thread.sleep(sleepTime);
	        }
	    }
	}

	public Integer getMachineId() {
		return machineId;
	}

	public void setMachineId(Integer machineId) {
		this.machineId = machineId;
	}

	public Integer getStatus() {
		return status;
	}

	public void setStatus(Integer status) {
		this.status = status;
	}

	public AttendanceService getAttendanceService() {
		return attendanceService;
	}

	public void setAttendanceService(AttendanceService attendanceService) {
		this.attendanceService = attendanceService;
	}

    /**
     * @return the reConnCount
     */
    public int getReConnCount() {
        return reConnCount;
    }

    /**
     * @param reConnCount the reConnCount to set
     */
    public void addReConnCount() {
        if (reConnCount == Integer.MAX_VALUE) {
            this.reConnCount = 0;
        }
        this.reConnCount += 1;
    }
    
    /**
     * 清空重连计数
     */
    public void clearReConnCount() {
        this.reConnCount = 1;
    }
}
